package com.ec50n9.server.common

import org.springframework.beans.BeanUtils
import java.time.LocalDateTime
import java.time.format.DateTimeFormatterBuilder
import java.time.temporal.ChronoField
import java.util.*
import kotlin.reflect.full.memberProperties

private fun getOrdinal(n: Int) = when {
    n in 11..13 -> "${n}th"
    n % 10 == 1 -> "${n}st"
    n % 10 == 2 -> "${n}nd"
    n % 10 == 3 -> "${n}rd"
    else -> "${n}th"
}

private val daysLookup = (1..31).associate { it.toLong() to getOrdinal(it) }

private val englishDateFormatter = DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd")
    .appendLiteral(" ")
    .appendText(ChronoField.DAY_OF_MONTH, daysLookup)
    .appendLiteral(" ")
    .appendPattern("yyyy")
    .toFormatter(Locale.ENGLISH)

fun LocalDateTime.format(): String = this.format(englishDateFormatter)

fun String.toSlug() = lowercase(Locale.getDefault())
    .replace("\n", " ")
    .replace("[^a-z\\d\\s]".toRegex(), " ")
    .split(" ")
    .joinToString("-")
    .replace("-+".toRegex(), "-")

/**
 * 列表对象属性拷贝
 */
inline fun <S : Any, reified T : Any> Iterable<S>.render(noinline adjust: ((source: S, target: T) -> Unit)? = null) =
    map { source -> source.render(adjust = adjust) }

/**
 * 对象属性拷贝
 */
inline fun <S : Any, reified T : Any> S.render(
    target: T? = null,
    excludeNullProperty: Boolean = false,
    noinline adjust: ((source: S, target: T) -> Unit)? = null
) =
    try {
        val obj = target ?: BeanUtils.instantiateClass(T::class.java)
        //val obj = target ?: T::class.java.getDeclaredConstructor().newInstance()
        val excludeNullProperties =
            if (excludeNullProperty) getNullMemberPropertyNames()
            else emptyList()
        BeanUtils.copyProperties(this, obj, *excludeNullProperties.toTypedArray())
        if (adjust != null) {
            adjust(this, obj)
        }
        obj
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }

/**
 * 获取当前对象值为空的属性
 */
fun Any.getNullMemberPropertyNames() =
    this::class.memberProperties
        .filter { it.call(this) == null }
        .map { it.name }
